#pragma once
#include "util.hpp"
#include "online.hpp"
#include "session.hpp"
#include "room.hpp"
#include "match.hpp"
#include "db.hpp"

#define WWWROOT "./wwwroot" // web根目录

class gobang_server
{
private:
    wsserver_t _wssvr;
    std::string _webroot; // web根目录
    user_table _ut;
    online_user _ou;
    room_manager _rm;
    session_manager _sm;
    matcher _match;

private:
    // 处理静态资源获取请求
    void file_handler(wsserver_t::connection_ptr &conn)
    {
        // 1.获取请求
        websocketpp::http::parser::request req = conn->get_request();
        std::string method = req.get_method();
        std::string uri = req.get_uri();
        // 2.获取静态资源文件路径,如果请求的是目录，那么就返回登陆页面
        std::string filepath = _webroot + uri;
        if (filepath.back() == '/')
        {
            filepath += "login.html";
        }
        // 3.读取文件
        std::string body;
        bool ret = file_util::read(filepath, body);
        //  如果请求的资源不存在，就返回404
        if (ret == false)
        {
            body = "<html><head><meta charset='UTF-8'/></head><body><h1>NOT FOUND</h1></body></html>";
            conn->set_body(body);
            conn->set_status(websocketpp::http::status_code::ok);
            return;
        }
        // 4.设置相应状态码以及正文
        conn->set_status(websocketpp::http::status_code::ok);
        conn->set_body(body);
    }
    void http_resp(bool result, const std::string reason, wsserver_t::connection_ptr &conn, websocketpp::http::status_code::value code)
    {
        Json::Value resp;
        resp["result"] = result;
        resp["reason"] = reason;
        std::string resp_body;
        // 序列化
        json_util::serialize(resp, resp_body);
        conn->set_body(resp_body);
        conn->set_status(code);
        conn->append_header("Content-Type", "application/json");
        return;
    }
    // 处理用户注册请求
    void reg(wsserver_t::connection_ptr &conn)
    {
        // 1.获取请求
        websocketpp::http::parser::request req = conn->get_request();
        // 2.获取请求正文，反序列化
        std::string body = conn->get_request_body();
        Json::Value login_info;
        bool ret = json_util::unserialize(body, login_info);
        if (ret == false)
        {
            DLOG("反序列失败!");
            return http_resp(false, "请求正文格式错误", conn, websocketpp::http::status_code::bad_request);
        }
        // 3.在数据库中插入新的用户信息
        if (login_info["username"].isNull() || login_info["password"].isNull())
        {
            DLOG("用户名或密码不完整!");
            return http_resp(false, "请输入用户名", conn, websocketpp::http::status_code::bad_request);
        }
        ret = _ut.insert(login_info);
        if (ret == false)
        {
            //  插入失败说明已经有用户名
            DLOG("用户名冲突,向数据库插入数据失败!");
            return http_resp(false, "用户名冲突", conn, websocketpp::http::status_code::bad_request);
        }
        // 4.返回
        return http_resp(true, "注册成功", conn, websocketpp::http::status_code::ok);
    }
    // 处理用户登陆请求
    void login(wsserver_t::connection_ptr &conn)
    {
        // 1.获取请求正文，并进行反序列化
        websocketpp::http::parser::request req = conn->get_request();
        std::string body = conn->get_request_body();
        Json::Value login_info;
        bool ret = json_util::unserialize(body, login_info);
        if (ret == false)
        {
            DLOG("反序列失败!");
            return http_resp(false, "请求正文格式错误", conn, websocketpp::http::status_code::bad_request);
        }
        // 2.验证正文的完整性，并对通过数据库核查
        if (login_info["username"].isNull() || login_info["password"].isNull())
        {
            DLOG("用户名或密码不完整!");
            return http_resp(false, "请输入用户名", conn, websocketpp::http::status_code::bad_request);
        }
        ret = _ut.login(login_info);
        if (ret == false)
        {
            DLOG("用户名或密码错误!");
            return http_resp(false, "请输入用户名", conn, websocketpp::http::status_code::bad_request);
        }
        // 3.建立session
        uint64_t uid = login_info["id"].asUInt64();
        session_ptr sp = _sm.session_create(uid, LOGIN);
        if (sp.get() == nullptr)
        {
            DLOG("添加session失败!");
            return http_resp(false, "创建会话失败", conn, websocketpp::http::status_code::bad_request);
        }
        // 建立会话完成后要设置时间
        _sm.set_session_expire_time(sp->get_ssid(), SESSION_TIMEOUT);
        // 4.添加cookie，设置响应头部字段Set-Cookie
        std::string cookie = "SSID=" + std::to_string(sp->get_ssid());
        conn->append_header("Set-Cookie", cookie);
        return http_resp(true, "登陆成功", conn, websocketpp::http::status_code::ok);
    }
    bool get_cookie_val(const std::string &cookie_str, const std::string &key, std::string &val)
    {
        // 以; 为单位去划分，然后找到SSID信息
        std::string sep = "; ";
        std::vector<std::string> vs;
        string_util::spilt(cookie_str, sep, vs);
        // 从vs中找到SSID
        for (auto &str : vs)
        {
            // 这里以"="分割,得到val
            std::vector<std::string> tmp_str;
            string_util::spilt(str, "=", tmp_str);
            if (tmp_str.size() != 2)
            {
                continue;
            }
            if (tmp_str[0] == "SSID")
            {
                val = tmp_str[1];
                return true;
            }
        }
        return false;
    }
    // 处理用户获取用户信息请求
    void info(wsserver_t::connection_ptr &conn)
    {
        // 1.获取请求正文中的Cookie信息，并通过cookie获得ssid
        std::string cookie_str = conn->get_request_header("Cookie");
        if (cookie_str.empty())
        {
            // 获取失败，重新登陆
            return http_resp(false, "获得Cookie信息失败,请重新登陆!", conn, websocketpp::http::status_code::bad_request);
        }
        std::string ssid_str;
        bool ret = get_cookie_val(cookie_str, "SSID", ssid_str);
        if (ret == false)
        {
            return http_resp(false, "获取SSID失败,请重新登陆!", conn, websocketpp::http::status_code::bad_request);
        }
        // 2.通过ssid获得会话信息
        session_ptr sp = _sm.get_session_by_sid(std::stol(ssid_str));
        if (sp.get() == nullptr)
        {
            return http_resp(false, "获取session信息失败,请重新登陆!", conn, websocketpp::http::status_code::bad_request);
        }
        uint64_t uid = sp->get_uid();
        // 3.在数据库中找到用户信息，并进行序列化
        Json::Value resp;
        ret = _ut.select_by_id(uid, resp);
        if (ret == false)
        {
            return http_resp(false, "找不到用户信息,请重新登陆!", conn, websocketpp::http::status_code::bad_request);
        }
        std::string body;
        json_util::serialize(resp, body);
        conn->set_body(body);
        conn->set_status(websocketpp::http::status_code::ok);
        conn->append_header("Content-Type", "application/json");
        // 4.刷新session过期时间
        _sm.set_session_expire_time(sp->get_ssid(), SESSION_TIMEOUT);
    }

    void http_callback(websocketpp::connection_hdl hdl)
    {
        wsserver_t::connection_ptr conn = _wssvr.get_con_from_hdl(hdl);
        websocketpp::http::parser::request req = conn->get_request();
        std::string method = req.get_method();
        std::string uri = req.get_uri();
        if (method == "POST" && uri == "/reg")
            reg(conn);
        else if (method == "POST" && uri == "/login")
            login(conn);
        else if (method == "GET" && uri == "/info")
            info(conn);
        else
            file_handler(conn);
    }
    void ws_resp(wsserver_t::connection_ptr conn, Json::Value resp)
    {
        std::string body;
        json_util::serialize(resp, body);
        conn->send(body);
    }
    session_ptr get_session_by_cookie(wsserver_t::connection_ptr conn)
    {
        // 1.查看客户端是否成功登陆
        Json::Value err_resp;
        // 1.获取请求正文中的Cookie信息，并通过cookie获得ssid
        std::string cookie_str = conn->get_request_header("Cookie");
        if (cookie_str.empty())
        {
            // 获取失败，重新登陆
            err_resp["optype"] = "hall_ready";
            err_resp["reason"] = "获得Cookie信息失败,请重新登陆!";
            err_resp["result"] = false;
            ws_resp(conn, err_resp);
            return session_ptr();
        }
        std::string ssid_str;
        bool ret = get_cookie_val(cookie_str, "SSID", ssid_str);
        if (ret == false)
        {
            err_resp["optype"] = "hall_ready";
            err_resp["reason"] = "获取SSID失败,请重新登陆!";
            err_resp["result"] = false;
            ws_resp(conn, err_resp);
            return session_ptr();
        }
        // 2.通过ssid获得会话信息
        session_ptr sp = _sm.get_session_by_sid(std::stol(ssid_str));
        if (sp.get() == nullptr)
        {
            err_resp["optype"] = "hall_ready";
            err_resp["reason"] = "获取session信息失败,请重新登陆!";
            err_resp["result"] = false;
            ws_resp(conn, err_resp);
            return session_ptr();
        }
        return sp;
    }
    void ws_game_hall(wsserver_t::connection_ptr conn)
    {
        // 1.查看客户端是否成功登陆
        Json::Value err_resp;
        session_ptr sp = get_session_by_cookie(conn);
        if (sp.get() == nullptr)
        {
            return;
        }
        // 2.验证客户端是否重复登陆
        if (_ou.is_in_game_hall(sp->get_uid()) || _ou.get_conn_from_room(sp->get_uid()))
        {
            err_resp["optype"] = "hall_ready";
            err_resp["reason"] = "用户重复登陆!";
            err_resp["result"] = false;
            return ws_resp(conn, err_resp);
        }
        // 3.将当前连接放入在线用户管理中
        _ou.enter_game_hall(sp->get_uid(), conn);
        // 4.给客户端响应进入游戏大厅
        Json::Value resp;
        resp["optype"] = "hall_ready";
        resp["result"] = true;
        ws_resp(conn, resp);
        // 5.将连接改成长连接
        _sm.set_session_expire_time(sp->get_ssid(), SESSION_FOREVER);
    }
    void ws_game_room(wsserver_t::connection_ptr conn)
    {
        // 1.获取用户的会话信息
        Json::Value resp;
        session_ptr sp = get_session_by_cookie(conn);
        if (sp.get() == nullptr)
        {
            return;
        }
        // 2.判断用户是否在其他的游戏大厅或者游戏房间中 -- 判断用户是否重复登陆
        if (_ou.is_in_game_hall(sp->get_uid()) || _ou.get_conn_from_room(sp->get_uid()))
        {
            resp["optype"] = "room_ready";
            resp["reason"] = "用户重复登陆!";
            resp["result"] = false;
            return ws_resp(conn, resp);
        }
        // 3.查看是否给用户创建了游戏房间
        room_ptr rp = _rm.get_room_by_uid(sp->get_uid());
        if (rp.get() == nullptr)
        {
            resp["optype"] = "room_ready";
            resp["reason"] = "没有找到用户的房间信息";
            resp["result"] = false;
            return ws_resp(conn, resp);
        }
        // 4.把用户添加到在线管理模块中的房间管理
        _ou.enter_game_room(sp->get_uid(), conn);
        // 5.把连接设置成永久存在
        _sm.set_session_expire_time(sp->get_ssid(), SESSION_FOREVER);
        // 6.发送给用户
        resp["optype"] = "room_ready";
        resp["result"] = true;
        resp["room_id"] = (Json::UInt64)rp->id();
        resp["uid"] = (Json::UInt64)sp->get_uid();
        resp["white_id"] = (Json::UInt64)rp->get_white_user();
        resp["black_id"] = (Json::UInt64)rp->get_black_user();
        return ws_resp(conn, resp);
    }
    void open_callback(websocketpp::connection_hdl hdl)
    {
        // 通过uri的不同进行判定，分别处理游戏大厅和游戏房间
        wsserver_t::connection_ptr conn = _wssvr.get_con_from_hdl(hdl);
        websocketpp::http::parser::request req = conn->get_request();
        std::string uri = req.get_uri();
        if (uri == "/hall")
        {
            ws_game_hall(conn);
        }
        else if (uri == "/room")
        {
            ws_game_room(conn);
        }
    }
    void wsclose_game_hall(wsserver_t::connection_ptr conn)
    {
        // 获取到会话信息
        Json::Value err_resp;
        session_ptr sp = get_session_by_cookie(conn);
        if (sp.get() == nullptr)
        {
            return;
        }
        // 1.将用户移除游戏大厅
        _ou.exit_game_hall(sp->get_uid());
        // 2.将永久连接变成短连接
        _sm.set_session_expire_time(sp->get_uid(), SESSION_TIMEOUT);
    }
    void wsclose_game_room(wsserver_t::connection_ptr conn){
        //1.获取会话信息
        Json::Value resp;
        session_ptr sp = get_session_by_cookie(conn);
        if (sp.get() == nullptr)
        {
            return;
        }
        //2.将玩家从在线用户管理房间模块中移除
        _ou.exit_game_room(sp->get_uid());
        //3.将session时间设置成定时删除
        _sm.set_session_expire_time(sp->get_ssid(),SESSION_TIMEOUT);
        //4.将该用户移除房间
        _rm.delete_room_user(sp->get_uid());
    }
    void close_callback(websocketpp::connection_hdl hdl)
    {
        // 通过uri的不同进行判定，分别处理游戏大厅和游戏房间
        wsserver_t::connection_ptr conn = _wssvr.get_con_from_hdl(hdl);
        websocketpp::http::parser::request req = conn->get_request();
        std::string uri = req.get_uri();
        if (uri == "/hall")
        {
            wsclose_game_hall(conn);
        }
        else if (uri == "/room")
        {
            wsclose_game_room(conn);
        }
    }
    void wsmsg_game_hall(wsserver_t::connection_ptr conn, wsserver_t::message_ptr msg)
    {
        // 1.获取用户会话信息
        Json::Value resp;
        session_ptr sp = get_session_by_cookie(conn);
        if (sp.get() == nullptr)
        {
            return;
        }
        // 2.将信息进行反序列化
        std::string req_body = msg->get_payload();
        Json::Value req;
        bool ret = json_util::unserialize(req_body, req);
        if (ret == false)
        {
            resp["result"] = false;
            resp["reason"] = "信息解析失败";
            ws_resp(conn, resp);
            return;
        }
        // 3.分别出来匹配和停止匹配
        if (!req["optype"].isNull() && req["optype"].asString() == "match_start")
        {
            //   开始匹配：将用户信息添加到匹配队列中
            _match.add(sp->get_uid());
            resp["optype"] = "match_start";
            resp["result"] = true;
            return ws_resp(conn, resp);
        }
        else if (!req["optype"].isNull() && req["optype"].asString() == "match_stop")
        {
            //   停止匹配：将用户从匹配队列中删除
            _match.del(sp->get_uid());
            resp["optype"] = "match_stop";
            resp["result"] = true;
            return ws_resp(conn, resp);
        }
        resp["optype"] = "unknow";
        resp["reason"] = "未知相应类型";
        resp["result"] = false;
        return ws_resp(conn, resp);
    }
    void wsmsg_game_room(wsserver_t::connection_ptr conn, wsserver_t::message_ptr msg)
    {
        //1.获取用户会话信息
        Json::Value resp;
        session_ptr sp = get_session_by_cookie(conn);
        if (sp.get() == nullptr)
        {
            return;
        }
        //2.获取用户房间信息
        room_ptr rp = _rm.get_room_by_uid(sp->get_uid());
        if (rp.get() == nullptr)
        {
            resp["optype"] = "unknow";
            resp["reason"] = "没有找到用户的房间信息";
            resp["result"] = false;
            return ws_resp(conn, resp);
        }
        //3.将信息进行反序列化
        Json::Value json_resp;
        std::string resp_body = msg->get_payload();
        bool ret = json_util::unserialize(resp_body,json_resp);
        if(ret == false)
        {
            resp["optype"] = "unknow";
            resp["reason"] = "解析请求失败";
            resp["result"] = false;
            DLOG("反序列化失败 : %s",resp_body);
            return ws_resp(conn, resp);
        }
        //4.通过房间管理模块处理信息
        DLOG("获取到信息 : %s",resp_body);
        rp->hander_request(json_resp);
    }
    void message_callback(websocketpp::connection_hdl hdl, wsserver_t::message_ptr msg)
    {
        // 通过uri的不同进行判定，分别处理游戏大厅和游戏房间
        wsserver_t::connection_ptr conn = _wssvr.get_con_from_hdl(hdl);
        websocketpp::http::parser::request req = conn->get_request();
        std::string uri = req.get_uri();
        if (uri == "/hall")
        {
            wsmsg_game_hall(conn, msg);
        }
        else if (uri == "/room")
        {
            wsmsg_game_room(conn, msg);
        }
    }

public:
    // 用来初始化服务器
    gobang_server(const std::string &host, const std::string &username, const std::string &password,
                  const std::string &dbname, uint16_t port, const std::string &webroot = WWWROOT)
        : _ut(host, username, password, dbname, port), _rm(&_ou, &_ut), _sm(&_wssvr), _match(&_ut, &_ou, &_rm), _webroot(webroot)
    {
        _wssvr.set_access_channels(websocketpp::log::alevel::none);
        // 设置调度器asio
        _wssvr.init_asio();
        _wssvr.set_reuse_addr(true);
        // 设置回调函数
        _wssvr.set_http_handler(std::bind(&gobang_server::http_callback, this, std::placeholders::_1));
        _wssvr.set_open_handler(std::bind(&gobang_server::open_callback, this, std::placeholders::_1));
        _wssvr.set_close_handler(std::bind(&gobang_server::close_callback, this, std::placeholders::_1));
        _wssvr.set_message_handler(std::bind(&gobang_server::message_callback, this, std::placeholders::_1, std::placeholders::_2));
    }
    // 启动服务器
    void start(int port = 3389)
    {
        // 监听
        _wssvr.listen(port);
        // 建立新连接
        _wssvr.start_accept();
        // 启动服务器
        _wssvr.run();
    }
};